﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using ECommon.InModel;

namespace ECommon
{
    /// <summary>
    /// 文件日志工具
    /// </summary>
    public static partial class FileLogTool
    {
        /// <summary>
        /// Trace输出
        /// </summary>
        /// <param name="ex">Trace异常</param>
        /// <param name="logDirOrPath">绝对的日志目录或路径</param>
        /// <param name="logConfigFilePath">日志配置文件的绝对路径</param>
        public static void Trace(Exception ex, string logDirOrPath = null, string logConfigFilePath = null)
        {
            if (ex == null) { return; }
            string invokeStr = ex.TargetSite == null ? "空" : ex.TargetSite.Name;
            string detailStr = ex.InnerException == null ? "空" : ex.InnerException.Message;
            WriteLog(LogLevel.Trace, ex.Message, ex.StackTrace, ex.Source, invokeStr, detailStr, logDirOrPath, logConfigFilePath);
        }

        /// <summary>
        /// Trace输出
        /// </summary>
        /// <param name="msg">Trace消息</param>
        /// <param name="logDirOrPath">绝对的日志目录或路径</param>
        /// <param name="logConfigFilePath">日志配置文件的绝对路径</param>
        public static void Trace(string msg, string logDirOrPath = null, string logConfigFilePath = null)
        {
            if (string.IsNullOrEmpty(msg)) { return; }
            string source = "空";
            string invoke = "空";
            string trace = GetLogTraceInfo(0, out source, out invoke);
            trace = trace == null || string.IsNullOrEmpty(trace.Trim()) ? "空" : trace.Trim();
            WriteLog(LogLevel.Trace, msg, trace, source, invoke, "空", logDirOrPath, logConfigFilePath);
        }

        /// <summary>
        /// Debug输出
        /// </summary>
        /// <param name="ex">Debug异常</param>
        /// <param name="logDirOrPath">绝对的日志目录或路径</param>
        /// <param name="logConfigFilePath">日志配置文件的绝对路径</param>
        public static void Debug(Exception ex, string logDirOrPath = null, string logConfigFilePath = null)
        {
            if (ex == null) { return; }
            string invokeStr = ex.TargetSite == null ? "空" : ex.TargetSite.Name;
            string detailStr = ex.InnerException == null ? "空" : ex.InnerException.Message;
            WriteLog(LogLevel.Debug, ex.Message, ex.StackTrace, ex.Source, invokeStr, detailStr, logDirOrPath, logConfigFilePath);
        }

        /// <summary>
        /// Debug输出
        /// </summary>
        /// <param name="msg">Debug消息</param>
        /// <param name="logDirOrPath">绝对的日志目录或路径</param>
        /// <param name="logConfigFilePath">日志配置文件的绝对路径</param>
        public static void Debug(string msg, string logDirOrPath = null, string logConfigFilePath = null)
        {
            if (string.IsNullOrEmpty(msg)) { return; }
            string source = "空";
            string invoke = "空";
            string trace = GetLogTraceInfo(0, out source, out invoke);
            trace = trace == null || string.IsNullOrEmpty(trace.Trim()) ? "空" : trace.Trim();
            WriteLog(LogLevel.Debug, msg, trace, source, invoke, "空", logDirOrPath, logConfigFilePath);
        }

        /// <summary>
        /// Info输出
        /// </summary>
        /// <param name="ex">Info异常</param>
        /// <param name="logDirOrPath">绝对的日志目录或路径</param>
        /// <param name="logConfigFilePath">日志配置文件的绝对路径</param>
        public static void Info(Exception ex, string logDirOrPath = null, string logConfigFilePath = null)
        {
            if (ex == null) { return; }
            string invokeStr = ex.TargetSite == null ? "空" : ex.TargetSite.Name;
            string detailStr = ex.InnerException == null ? "空" : ex.InnerException.Message;
            WriteLog(LogLevel.Info, ex.Message, ex.StackTrace, ex.Source, invokeStr, detailStr, logDirOrPath, logConfigFilePath);
        }

        /// <summary>
        /// Info输出
        /// </summary>
        /// <param name="msg">Info消息</param>
        /// <param name="logDirOrPath">绝对的日志目录或路径</param>
        /// <param name="logConfigFilePath">日志配置文件的绝对路径</param>
        public static void Info(string msg, string logDirOrPath = null, string logConfigFilePath = null)
        {
            if (string.IsNullOrEmpty(msg)) { return; }
            string source = "空";
            string invoke = "空";
            string trace = GetLogTraceInfo(0, out source, out invoke);
            trace = trace == null || string.IsNullOrEmpty(trace.Trim()) ? "空" : trace.Trim();
            WriteLog(LogLevel.Info, msg, trace, source, invoke, "空", logDirOrPath, logConfigFilePath);
        }

        /// <summary>
        /// Warning输出
        /// </summary>
        /// <param name="ex">Warning异常</param>
        /// <param name="logDirOrPath">绝对的日志目录或路径</param>
        /// <param name="logConfigFilePath">日志配置文件的绝对路径</param>
        public static void Warning(Exception ex, string logDirOrPath = null, string logConfigFilePath = null)
        {
            if (ex == null) { return; }
            string invokeStr = ex.TargetSite == null ? "空" : ex.TargetSite.Name;
            string detailStr = ex.InnerException == null ? "空" : ex.InnerException.Message;
            WriteLog(LogLevel.Warning, ex.Message, ex.StackTrace, ex.Source, invokeStr, detailStr, logDirOrPath, logConfigFilePath);
        }

        /// <summary>
        /// Warning输出
        /// </summary>
        /// <param name="msg">Warning消息</param>
        /// <param name="logDirOrPath">绝对的日志目录或路径</param>
        /// <param name="logConfigFilePath">日志配置文件的绝对路径</param>
        public static void Warning(string msg, string logDirOrPath = null, string logConfigFilePath = null)
        {
            if (string.IsNullOrEmpty(msg)) { return; }
            string source = "空";
            string invoke = "空";
            string trace = GetLogTraceInfo(0, out source, out invoke);
            trace = trace == null || string.IsNullOrEmpty(trace.Trim()) ? "空" : trace.Trim();
            WriteLog(LogLevel.Warning, msg, trace, source, invoke, "空", logDirOrPath, logConfigFilePath);
        }

        /// <summary>
        /// Error输出
        /// </summary>
        /// <param name="ex">Error异常</param>
        /// <param name="logDirOrPath">绝对的日志目录或路径</param>
        /// <param name="logConfigFilePath">日志配置文件的绝对路径</param>
        public static void Error(Exception ex, string logDirOrPath = null, string logConfigFilePath = null)
        {
            if (ex == null) { return; }
            string invokeStr = ex.TargetSite == null ? "空" : ex.TargetSite.Name;
            string detailStr = ex.InnerException == null ? "空" : ex.InnerException.Message;
            WriteLog(LogLevel.Error, ex.Message, ex.StackTrace, ex.Source, invokeStr, detailStr, logDirOrPath, logConfigFilePath);
        }

        /// <summary>
        /// Error输出
        /// </summary>
        /// <param name="msg">Error消息</param>
        /// <param name="logDirOrPath">绝对的日志目录或路径</param>
        /// <param name="logConfigFilePath">日志配置文件的绝对路径</param>
        public static void Error(string msg, string logDirOrPath = null, string logConfigFilePath = null)
        {
            if (string.IsNullOrEmpty(msg)) { return; }
            string source = "空";
            string invoke = "空";
            string trace = GetLogTraceInfo(0, out source, out invoke);
            trace = trace == null || string.IsNullOrEmpty(trace.Trim()) ? "空" : trace.Trim();
            WriteLog(LogLevel.Error, msg, trace, source, invoke, "空", logDirOrPath, logConfigFilePath);
        }

        /// <summary>
        /// Fatal输出
        /// </summary>
        /// <param name="ex">Fatal异常</param>
        /// <param name="logDirOrPath">绝对的日志目录或路径</param>
        /// <param name="logConfigFilePath">日志配置文件的绝对路径</param>
        public static void Fatal(Exception ex, string logDirOrPath = null, string logConfigFilePath = null)
        {
            if (ex == null) { return; }
            string invokeStr = ex.TargetSite == null ? "空" : ex.TargetSite.Name;
            string detailStr = ex.InnerException == null ? "空" : ex.InnerException.Message;
            WriteLog(LogLevel.Fatal, ex.Message, ex.StackTrace, ex.Source, invokeStr, detailStr, logDirOrPath, logConfigFilePath);
        }

        /// <summary>
        /// Fatal输出
        /// </summary>
        /// <param name="msg">Fatal消息</param>
        /// <param name="logDirOrPath">绝对的日志目录或路径</param>
        /// <param name="logConfigFilePath">日志配置文件的绝对路径</param>
        public static void Fatal(string msg, string logDirOrPath = null, string logConfigFilePath = null)
        {
            if (string.IsNullOrEmpty(msg)) { return; }
            string source = "空";
            string invoke = "空";
            string trace = GetLogTraceInfo(0, out source, out invoke);
            trace = trace == null || string.IsNullOrEmpty(trace.Trim()) ? "空" : trace.Trim();
            WriteLog(LogLevel.Fatal, msg, trace, source, invoke, "空", logDirOrPath, logConfigFilePath);
        }

        /// <summary>
        /// 写入日志
        /// </summary>
        /// <param name="logLevel">日志级别</param>
        /// <param name="msg">消息</param>
        /// <param name="traceStr">追溯</param>
        /// <param name="sourceStr">来源</param>
        /// <param name="invokeStr">调用</param>
        /// <param name="detailStr">详细</param>
        /// <param name="logDirOrPath">绝对的日志目录或路径</param>
        /// <param name="logConfigFilePath">日志配置文件的绝对路径</param>
        private static void WriteLog(LogLevel logLevel, string msg, string traceStr, string sourceStr, string invokeStr, string detailStr, string logDirOrPath = null, string logConfigFilePath = null)
        {
            ReadFileLogConfig(logConfigFilePath);
            if (_logConfig.Level > logLevel)
            {
                return;
            }
            string logDir = "";
            DateTime logTime = DateTime.Now;
            string logName = "";
            if (FileTool.IsFilePath(logDirOrPath))
            {
                //是文件路径
                logDir = Path.GetDirectoryName(logDirOrPath);
                logName = Path.GetFileName(logDirOrPath);
            }
            else
            {
                if (FileTool.IsDir(logDirOrPath))
                {
                    logDir = logDirOrPath;
                }
                else
                {
                    logDir = Path.Combine(_logConfig.Dir, logTime.ToString("yyyyMMdd"));
                }
                logName = logLevel.ToString() + ".txt";
            }
            RemoveExpireLog(logDir, _logConfig.Expire);
            string filePath = Path.Combine(logDir, logName);
            string logMsg = CreateMsg(logLevel, msg, traceStr, sourceStr, invokeStr, detailStr, logTime);
            WriteLogMsg(logMsg, filePath);
        }

        /// <summary>
        /// 构建日志消息
        /// </summary>
        /// <param name="logLevel">日志级别</param>
        /// <param name="msg">消息</param>
        /// <param name="traceStr">追溯</param>
        /// <param name="sourceStr">来源</param>
        /// <param name="invokeStr">调用</param>
        /// <param name="detailStr">详细</param>
        /// <param name="logTime">记录日志的时间</param>
        /// <returns>日志消息</returns>
        private static string CreateMsg(LogLevel logLevel, string msg, string traceStr, string sourceStr, string invokeStr, string detailStr, DateTime logTime)
        {
            StringBuilder sb = new StringBuilder();
            switch (_logConfig.Type)
            {
                case LogType.Compact:
                    sb.Append(logLevel);
                    sb.Append("\t");

                    sb.Append(logTime.ToString("yyyy-MM-dd HH:mm:ss.fff"));
                    sb.Append("\t");

                    sb.Append(LineFmt(ExpandLine(msg)));
                    sb.Append("\t");

                    sb.Append(LineFmt(ExpandLine(traceStr)));
                    sb.Append("\t");

                    sb.Append(LineFmt(ExpandLine(sourceStr)));
                    sb.Append("\t");

                    sb.Append(LineFmt(ExpandLine(invokeStr)));
                    sb.Append("\t");

                    sb.Append(LineFmt(ExpandLine(detailStr)));
                    sb.Append("\r\n");
                    break;
                case LogType.Detail:
                    sb.Append("【类型】：");
                    sb.Append(logLevel);
                    sb.Append("\t");

                    sb.Append("【时间】：");
                    sb.Append(logTime.ToString("yyyy-MM-dd HH:mm:ss.fff"));
                    sb.Append("\t");

                    sb.Append("【消息】：");
                    sb.Append(LineFmt(ExpandLine(msg)));
                    sb.Append("\t");

                    sb.Append("【追溯】：");
                    sb.Append(LineFmt(ExpandLine(traceStr)));
                    sb.Append("\t");

                    sb.Append("【来源】：");
                    sb.Append(LineFmt(ExpandLine(sourceStr)));
                    sb.Append("\t");

                    sb.Append("【调用】：");
                    sb.Append(LineFmt(ExpandLine(invokeStr)));
                    sb.Append("\t");

                    sb.Append("【详细】：");
                    sb.Append(LineFmt(ExpandLine(detailStr)));
                    sb.Append("\r\n");
                    break;
                case LogType.Easy:
                default:
                    sb.Append("【类型】=");
                    sb.Append(logLevel);
                    sb.Append("\t");

                    sb.Append("【时间】=");
                    sb.Append(logTime.ToString("yyyy-MM-dd HH:mm:ss.fff"));
                    sb.Append("\t");

                    sb.Append("【消息】=");
                    sb.Append(LineFmt(ExpandLine(msg)));
                    sb.Append("\r\n");
                    break;
            }
            return sb.ToString();
        }

        /// <summary>
        /// 写入日志消息
        /// </summary>
        /// <param name="logMsg">日志消息</param>
        /// <param name="logPath">日志绝对路径</param>
        private static void WriteLogMsg(string logMsg, string logPath)
        {
            lock (_logLock)
            {
                if (File.Exists(logPath))
                {
                    FileInfo fi = new FileInfo(logPath);
                    if (fi.Length <= 0 && _logConfig.Type == LogType.Compact)
                    {
                        FileTool.Append("【类型】\t【时间】\t【消息】\t【追溯】\t【来源】\t【调用】\t【详细】\r\n", logPath);
                    }
                }
                FileTool.Append(logMsg, logPath);
            }
        }

        /// <summary>
        /// 移除过期日志
        /// </summary>
        /// <param name="logDir">日志目录</param>
        /// <param name="expire">过期时间</param>
        private static void RemoveExpireLog(string logDir, int expire)
        {
            if (!Directory.Exists(logDir))
            {
                return;
            }
            List<string> itemDirs = Directory.GetDirectories(logDir)?.ToList();
            if (!int.TryParse(DateTime.Now.AddDays(expire * -1).ToString("yyyyMMdd"), out int timeBoundary))
            {
                return;
            }
            foreach (string dir in itemDirs)
            {
                DirectoryInfo dirInfo = new DirectoryInfo(dir);
                if (dirInfo.Exists)
                {
                    if (!int.TryParse(dirInfo.CreationTime.ToString("yyyyMMdd"), out int timeTag))
                    {
                        continue;
                    }
                    if (timeTag < timeBoundary)
                    {
                        Directory.Delete(dir, true);
                    }
                }
            }
            List<string> files = Directory.GetFiles(logDir)?.ToList();
            foreach (string file in files)
            {
                FileInfo fileInfo = new FileInfo(file);
                if (fileInfo.Exists)
                {
                    if (!int.TryParse(fileInfo.CreationTime.ToString("yyyyMMdd"), out int timeTag))
                    {
                        continue;
                    }
                    if (timeTag < timeBoundary)
                    {
                        File.Delete(file);
                    }
                }
            }
        }

        /// <summary>
        /// 日志锁
        /// </summary>
        private static readonly string _logLock = "logLock";
        /// <summary>
        /// 字符串格式化
        /// </summary>
        /// <param name="line">字符串</param>
        /// <returns>格式化后的字符串</returns>
        private static string LineFmt(string line)
        {
            return (string.IsNullOrEmpty(line)) ? "空" : line.Trim();
        }

        /// <summary>
        /// 多行字符串展开为单行
        /// </summary>
        /// <param name="lines">多行字符串</param>
        /// <returns>单行字符串</returns>
        private static string ExpandLine(string lines)
        {
            if (string.IsNullOrEmpty(lines))
            {
                return "空";
            }
            string lineTxt = string.Join(";", lines.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList().Select(a => a.Trim()).ToArray());
            if (string.IsNullOrEmpty(lineTxt))
            {
                return "NULL";
            }
            return lineTxt;
        }

        /// <summary>
        /// 获得日志追溯消息
        /// </summary>
        /// <param name="skipTrace">跳过的级别</param>
        /// <param name="source">日志来源消息，外传参数</param>
        /// <param name="invoke">日志调用消息，外传参数</param>
        /// <returns>追溯消息</returns>
        private static string GetLogTraceInfo(int skipTrace, out string source, out string invoke)
        {
            StackTrace trace = new StackTrace(skipTrace, true);
            source = GetSourceAndInvoke(trace, out invoke);
            return LineFmt(FormatTrace(trace.ToString()));
        }

        /// <summary>
        /// 日志配置
        /// </summary>
        private static string _logConfigFilePath = AppDomain.CurrentDomain.BaseDirectory + "Config\\LogConfig.xml";

        /// <summary>
        /// 日志配置
        /// </summary>
        private static FileLogConfig _logConfig = null;

        /// <summary>
        /// 读取文件日志配置文件
        /// </summary>
        /// <param name="logConfigFilePath">日志配置文件路径</param>
        /// <exception cref="Exception">日志配置文件丢失异常</exception>
        private static void ReadFileLogConfig(string logConfigFilePath = null)
        {
            if (string.IsNullOrEmpty(logConfigFilePath))
            {
                logConfigFilePath = _logConfigFilePath;
            }
            if (!File.Exists(logConfigFilePath))
            {
                _logConfig = new FileLogConfig();
                _logConfig.Dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log");
                _logConfig.Level = LogLevel.All;
                _logConfig.Type = LogType.Easy;
                _logConfig.Expire = 120;
                XmlTool.XmlSerialize(_logConfig, logConfigFilePath);
            }
            else
            {
                _logConfig = XmlTool.XmlDeserializeRef<FileLogConfig>(logConfigFilePath);
            }
        }

        /// <summary>
        /// 格式化追溯行
        /// </summary>
        /// <param name="traceLines">追溯行</param>
        /// <returns>格式化后的追溯行</returns>
        private static string FormatTrace(string traceLines)
        {
            if (traceLines == null || string.IsNullOrEmpty(traceLines.Trim()))
            {
                return null;
            }
            string selfStr = "";
            //取得当前代码的类名    
            selfStr = MethodBase.GetCurrentMethod()?.DeclaringType?.FullName;
            if (selfStr == null || string.IsNullOrEmpty(selfStr.Trim()))
            {
                selfStr = "空";
            }
            List<string> traces = traceLines.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList();
            List<string> filterTraces = traces.FindAll(a => (!a.Contains(selfStr)) && (a.Contains("cs:line") || a.Contains("行号")));
            if (filterTraces == null || filterTraces.Count <= 0)
            {
                return null;
            }
            else
            {
                return string.Join(";", filterTraces.ToArray());
            }
        }

        /// <summary>
        /// 根据追踪消息获取日志来源和调用
        /// </summary>
        /// <param name="trace">追溯消息</param>
        /// <param name="invoke">日志调用，外传参数</param>
        /// <returns>日志来源</returns>
        private static string GetSourceAndInvoke(StackTrace trace, out string invoke)
        {
            string selfFileName = System.Reflection.MethodBase.GetCurrentMethod()?.DeclaringType?.Name;
            string methodName = "空";
            string fileName = "空";
            for (int i = 0; i < trace.FrameCount; i++)
            {
                StackFrame myFrame = trace.GetFrame(i);
                if (myFrame == null)
                {
                    continue;
                }
                string fName = myFrame.GetFileName();
                if (fName == null || string.IsNullOrEmpty(fName.Trim()))
                {
                    continue;
                }
                string fTmp = Path.GetFileNameWithoutExtension(fName);
                if (fTmp == selfFileName)
                {
                    continue;
                }
                MethodBase mb = myFrame.GetMethod();
                methodName = mb == null ? "空" : mb.Name;
                fileName = myFrame.GetFileName();
                break;
            }
            invoke = (methodName == null || string.IsNullOrEmpty(methodName.Trim())) ? "空" : methodName.Trim();
            return (fileName == null || string.IsNullOrEmpty(fileName.Trim())) ? "空" : fileName.Trim();
        }
    }
}
